跳到主要内容

js 核心模块编译及运行流程

nodejs 的核心模块主要分为两种,一种是 js 核心模块,一种是 c/c++核心模块。 无论是 js 核心模块,还是 c/c++核心模块,都会提前进行编译。

转存阶段

单就 js 核心模块来说,会将其转存为 c/c++ 文件。转存的本质是将其包装进了 c/c++的字符串。然后将 c/c++编译成二进制进行存储,js 在二进制的.rodata段。

引入模块及 v8 引擎编译阶段

在运行阶段,require 引用模块时,会判断模块是否是原生模块,如果是原生模块,则会直接从 c/c++的内存中,获取 js 的代码,将其包装头尾,再通过v8 编译执行 js 代码,导出 exports对象。

完整流程

====== 编译阶段(构建 Node.js 时)======

┌─────────────────────────────────────┐
│ JS 核心模块源码 │
│ lib/fs.js │
│ lib/http.js │
│ lib/path.js │
│ ... (100+ 文件) │
└──────────┬──────────────────────────┘


┌──────────────┐
│ js2c.py 工具 │ ← 转换工具
└──────┬───────┘

▼ 【第一步:转存为 C++ 字符串】
┌─────────────────────────────────────┐
│ 生成 C++ 源文件 │
│ src/node_javascript.cc │
│ │
│ static const char fs_native[] = │
│ "function readFile() {...}"; │
│ │
│ static const char http_native[] = │
│ "function createServer() {...}"; │
└──────────┬──────────────────────────┘

▼ 【第二步:C++ 编译】
┌──────────────┐
│ g++ / clang │ ← C++ 编译器
└──────┬───────┘


┌─────────────────────────────────────┐
│ node 可执行文件(二进制) │
│ │
│ ELF Header │
│ ├── .text 段(机器码) │
│ ├── .rodata 段(只读数据) │
│ │ ├── fs_native[] ←── JS 源码! │
│ │ ├── http_native[] │
│ │ └── path_native[] │
│ ├── .data 段 │
│ └── ... │
└─────────────────────────────────────┘

====== 运行阶段(用户执行代码时)======

用户代码:
const fs = require('fs');

▼ 【判断模块类型】
┌──────────────┐
│ 是原生模块? │
└──┬────────┬──┘
YES NO
│ │
│ └──→ 按普通模块处理(读文件等)

▼ 【从 C++ 内存获取源码】
┌─────────────────────────────────────┐
│ NativeModule.getCached('fs') │
│ ↓ │
│ internalBinding('natives')['fs'] │ ← C++ 绑定
│ ↓ │
│ return fs_native; // C++ 字符串指针 │
└──────────┬──────────────────────────┘

▼ 【包装成模块函数】
┌─────────────────────────────────────┐
│ (function(exports, require, module, │
│ __filename, __dirname) { │
│ │
│ // ← 注入 JS 源码 │
│ function readFile() {...} │
│ module.exports = {...} │
│ │
│ }) │
└──────────┬──────────────────────────┘

▼ 【V8 编译执行】
┌──────────────┐
│ V8 引擎 │
│ - 解析 AST │
│ - 生成字节码 │
│ - JIT 编译 │
└──────┬───────┘


返回 module.exports

完整数据流

用户代码:require('fs')


┌────────────────────────────────────┐
│ Module._load('fs', parent, false) │
└─────────┬──────────────────────────┘

▼ 检查缓存
┌─────────────┐
│ 缓存中有吗? │
└──┬─────┬────┘
YES NO
│ │
│ ▼ 判断类型
│ ┌─────────────────┐
│ │ 是原生模块吗? │
│ └──┬──────────┬───┘
│ YES NO
│ │ │
│ │ └─→ 文件模块路径
│ │ - Module._resolveFilename()
│ │ - fs.readFileSync()
│ │ - Module._compile()
│ │
│ ▼
│ ┌─────────────────────────────┐
│ │ NativeModule.require('fs') │
│ └────────┬────────────────────┘
│ │
│ ▼ C++ 绑定调用
│ ┌─────────────────────────────┐
│ │ process.binding('natives') │ ← Node.js 内部 API
│ └────────┬────────────────────┘
│ │
│ ▼ C++ 层
│ ┌─────────────────────────────┐
│ │ GetNativeModule("fs") │
│ │ ↓ │
│ │ return fs_native; │ ← .rodata 段地址
│ │ (0x1000a0000) │
│ └────────┬────────────────────┘
│ │
│ ▼ JavaScript 层
│ ┌─────────────────────────────┐
│ │ source = │
│ │ "function readFile() {...}" │ ← JS 字符串
│ └────────┬────────────────────┘
│ │
│ ▼ 包装
│ ┌─────────────────────────────┐
│ │ wrapped = │
│ │ "(function(exports...) { │
│ │ function readFile() {...} │
│ │ })" │
│ └────────┬────────────────────┘
│ │
│ ▼ V8 编译
│ ┌─────────────────────────────┐
│ │ vm.compileFunction(wrapped) │
│ │ ↓ │
│ │ AST → 字节码 → 机器码 │
│ └────────┬────────────────────┘
│ │
│ ▼ 执行
│ ┌─────────────────────────────┐
│ │ fn(exports, require, ...) │
│ │ ↓ │
│ │ module.exports = {...} │
│ └────────┬────────────────────┘
│ │
│ ▼ 缓存
│ ┌─────────────────────────────┐
│ │ NativeModule._cache['fs'] │
│ │ = module.exports │
│ └────────┬────────────────────┘
│ │
└───────────┘


返回 fs 对象